package sql

import (
	"fmt"
	"io/ioutil"
	"path"
	"strconv"
	"strings"

	prompt "github.com/c-bata/go-prompt"
)

var (
	dynamicPrefix struct {
		LivePrefix   string
		IsEnable     bool
		nowTable     string
		nowHander    QueryOper
		cacheDisplay []Dict
		cacheCursor  int
		cacheLen     int
	}
)

type G map[string]string

type QueryOper interface {
	Query(name string, g G) []Dict
	Search(name, key string) []Dict
	GetKeys(table string) []string
	Ls() []string
}

func changeLivePrefix() (string, bool) {
	return dynamicPrefix.LivePrefix, dynamicPrefix.IsEnable
}

func lineToDict(line string) (d map[string]string) {
	d = make(map[string]string)
	for _, par := range strings.Split(line, ",") {
		if strings.Contains(par, "=") {
			vs := strings.SplitN(par, "=", 2)
			d[strings.TrimSpace(vs[0])] = strings.TrimSpace(vs[1])
		} else if strings.Contains(par, ":") {
			vs := strings.SplitN(par, ":", 2)
			d[strings.TrimSpace(vs[0])] = strings.TrimSpace(vs[1])
		} else {
			continue
		}
	}
	return
}

func completer(in prompt.Document) []prompt.Suggest {
	w := in.GetWordBeforeCursor()
	bw := in.TextBeforeCursor()
	// if strings.Contains(bw, "cd") {
	// 	fmt.Println(w, "|", bw)
	// }
	// fmt.Println("\n B:", bw, "\n--\n", w, "\n")

	if strings.Contains(bw, "cd") {
		suggestions := []prompt.Suggest{}
		for _, n := range dynamicPrefix.nowHander.Ls() {
			suggestions = append(suggestions, prompt.Suggest{
				Text:        strings.TrimSpace(n),
				Description: "table",
			})
		}
		// fmt.Println("[Test]", bw, "[TESN]")
		return filterSuggestions(suggestions, w, true, func(op, sub string) bool {
			// fmt.Println("\n [C]", op, sub, "[C]\n")

			return strings.Contains(op, sub)
		})
	} else if strings.Contains(bw, "query") {
		suggestions := []prompt.Suggest{}
		for _, n := range dynamicPrefix.nowHander.GetKeys(dynamicPrefix.nowTable) {
			suggestions = append(suggestions, prompt.Suggest{
				Text:        strings.TrimSpace(n),
				Description: "column",
			})
		}
		return filterSuggestions(suggestions, w, true, func(op, sub string) bool {
			// fmt.Println("\n", op, sub, "\n")
			return strings.Contains(op, sub)
		})
	} else if strings.Contains(bw, "set") {
		suggestions := []prompt.Suggest{
			prompt.Suggest{Text: "pageLen", Description: "set page length for each page: pageLen = xxx"},
		}
		return suggestions
	} else if strings.Contains(bw, "output") {
		if w != "" {
			return FilePathCompeleter(w)
		} else {
			return []prompt.Suggest{}
		}

	} else {
		sd := []prompt.Suggest{}
		if dynamicPrefix.nowTable != "" {
			sd = []prompt.Suggest{
				{Text: "cd", Description: "change table in this time"},
				{Text: "query", Description: "Query some data by 'query  k: some / k = some"},
				{Text: "search", Description: "Query some data by 'query  k: some / k = some"},
				{Text: "ls", Description: "Show all tables"},
				{Text: "set", Description: "Set some option"},
				{Text: "output", Description: "output to some path default in now 'example.xlsx' "},
				{Text: "page", Description: "show page num if empty will show next page| " + fmt.Sprintf("page at: %d/%d", dynamicPrefix.cacheCursor, len(dynamicPrefix.cacheDisplay)/dynamicPrefix.cacheLen)},
			}

		} else {
			sd = []prompt.Suggest{
				{Text: "cd", Description: "change table in this time"},
				{Text: "ls", Description: "Show all tables"},
			}

		}
		return prompt.FilterHasPrefix(sd, w, true)

	}

}
func ShowPage(pageStr string) {
	var page int
	var err error
	if pageStr == "" {
		page = dynamicPrefix.cacheCursor + 1
	} else {

		page, err = strconv.Atoi(pageStr)
		if err != nil {
			fmt.Println(err)
			return
		}
	}

	start := page * dynamicPrefix.cacheLen
	end := start + dynamicPrefix.cacheLen
	if end <= len(dynamicPrefix.cacheDisplay) {
		ColorT(dynamicPrefix.cacheDisplay[start:end])
		dynamicPrefix.cacheCursor = page
	} else {
		fmt.Println("Over page ")
	}
	UpdatePrefix()
}

func UpdatePrefix() {
	if dynamicPrefix.nowTable != "" {
		dynamicPrefix.LivePrefix = dynamicPrefix.nowTable + fmt.Sprintf("%d/%d > ", dynamicPrefix.cacheCursor, len(dynamicPrefix.cacheDisplay)/dynamicPrefix.cacheLen)
		dynamicPrefix.IsEnable = true
	}
}

func DoSearch(key string) {
	if dynamicPrefix.nowTable != "" {
		datas := dynamicPrefix.nowHander.Search(dynamicPrefix.nowTable, key)

		if len(datas) < dynamicPrefix.cacheLen-4 {
			dynamicPrefix.cacheDisplay = datas
			ColorT(datas)
		} else {
			dynamicPrefix.cacheDisplay = datas
			dynamicPrefix.cacheCursor = 0
			ShowPage("0")
		}

	}
}

func DoQuery(g G) {
	if dynamicPrefix.nowTable != "" {
		datas := dynamicPrefix.nowHander.Query(dynamicPrefix.nowTable, g)
		if len(datas) < dynamicPrefix.cacheLen-4 {
			dynamicPrefix.cacheDisplay = datas
			ColorT(datas)
		} else {
			dynamicPrefix.cacheDisplay = datas
			dynamicPrefix.cacheCursor = 0
			ShowPage("0")
		}

	}

}

func FilePathCompeleter(w string) (out []prompt.Suggest) {
	dir := path.Dir(w)
	name := path.Base(w)
	if fs, err := ioutil.ReadDir(dir); err == nil {
		if name == "" {
			for _, file := range fs {
				desc := " - Dir"
				if !file.IsDir() {
					desc = " - File"
				}
				out = append(out, prompt.Suggest{
					Text:        path.Join(dir, file.Name()),
					Description: desc,
				})
			}
			return
		}
		for _, file := range fs {
			if strings.HasPrefix(file.Name(), name) {
				desc := " - Dir"
				if !file.IsDir() {
					desc = " - File"
				}
				out = append(out, prompt.Suggest{
					Text:        path.Join(dir, file.Name()),
					Description: desc,
				})
			}
		}
	}
	return
}

func executor(in string) {
	fs := strings.Fields(in)
	if in == "" {
		DoSearch("")
		return
	}
	switch fs[0] {
	case "cd":
		if len(fs) > 1 {
			page := len(dynamicPrefix.cacheDisplay)/dynamicPrefix.cacheLen + 1
			dynamicPrefix.LivePrefix = fs[1] + fmt.Sprintf(" all page: %d >>", page)
			dynamicPrefix.IsEnable = true
			dynamicPrefix.nowTable = fs[1]
		} else {
			dynamicPrefix.LivePrefix = ">>"
			dynamicPrefix.IsEnable = true
			dynamicPrefix.nowTable = ""
		}
	case "ls":
		if dynamicPrefix.nowTable != "" {
			for _, v := range dynamicPrefix.nowHander.GetKeys(dynamicPrefix.nowTable) {
				fmt.Println(v)
			}
		} else {
			for _, v := range dynamicPrefix.nowHander.Ls() {
				fmt.Println(v)
			}
		}

	case "query":
		if len(fs) > 1 && dynamicPrefix.nowTable != "" {
			g := lineToDict(strings.Join(fs[1:], " "))
			fmt.Println("query key:", g)
			DoQuery(g)
		}
	case "page":
		if len(dynamicPrefix.cacheDisplay) > 0 {
			if len(fs) > 1 {
				ShowPage(fs[1])
			} else {
				ShowPage("")
			}
		}
	case "set":
		if len(fs) > 1 {
			if fs[1] == "pageLen" {
				if len(fs) > 2 {
					if num, err := strconv.Atoi(fs[2]); err == nil {
						dynamicPrefix.cacheLen = num
					}
				}
			}
		}
	case "output":
		if len(fs) > 1 {
			ExportToXlsx(fs[1], dynamicPrefix.cacheDisplay)
		} else {
			ExportToXlsx("example.xlsx", dynamicPrefix.cacheDisplay)
		}
	default:
		if strings.Contains(in, ":") || strings.Contains(in, "=") {
			if len(fs) > 1 && dynamicPrefix.nowTable != "" {
				g := lineToDict(in)
				DoQuery(g)
			}
		} else {
			if dynamicPrefix.nowTable != "" {
				DoSearch(in)
			}
		}
	}

}

func CliCmd(operHander QueryOper) {
	dynamicPrefix.nowHander = operHander
	dynamicPrefix.cacheLen = SCREEN_HEIGHT - 12
	p := prompt.New(
		executor,
		completer,
		prompt.OptionPrefix(">>> "),
		prompt.OptionLivePrefix(changeLivePrefix),
		prompt.OptionTitle("Sql Query System ..... Dr.Mroy"),
		prompt.OptionPrefixTextColor(prompt.Yellow),
		prompt.OptionPreviewSuggestionTextColor(prompt.Blue),
		prompt.OptionSelectedSuggestionBGColor(prompt.LightGray),
		prompt.OptionSuggestionBGColor(prompt.DarkGray),
	)
	p.Run()
}

func filterSuggestions(suggestions []prompt.Suggest, sub string, ignoreCase bool, function func(string, string) bool) []prompt.Suggest {
	if sub == "" {
		return suggestions
	}
	if ignoreCase {
		sub = strings.ToUpper(sub)
	}

	ret := make([]prompt.Suggest, 0, len(suggestions))
	for i := range suggestions {
		c := suggestions[i].Text
		if ignoreCase {
			c = strings.ToUpper(c)
		}
		if function(c, sub) {
			ret = append(ret, suggestions[i])
		}
	}
	return ret
}

func CompletedInput(opts ...string) string {

	Comp := func(in prompt.Document) []prompt.Suggest {
		s := []prompt.Suggest{}
		for _, opt := range opts {
			s = append(s, prompt.Suggest{Text: opt, Description: "OPtion " + opt})
		}

		return prompt.FilterFuzzy(s, in.GetWordBeforeCursor(), true)
	}

	return prompt.Input(">>> ",
		Comp,
		prompt.OptionTitle("table grep"),
		prompt.OptionPrefixTextColor(prompt.Yellow),
		prompt.OptionPreviewSuggestionTextColor(prompt.Blue),
		prompt.OptionSelectedSuggestionBGColor(prompt.LightGray),
		prompt.OptionSuggestionBGColor(prompt.DarkGray))

}
